// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System.Security.Principal;

namespace SSISAccess
{
    internal enum ConfigType
    {
        Unspecified,
        Client,
        Server
    }

    internal struct CmdLine
    {
        public bool parseSucceeded;
        public bool readOnly;
        public AccountInfo userAccount;
        public ConfigType configType;
    }

    internal enum RequiredAction
    {
        None,
        RestartSSIS,
        RestartMachine
    }

    internal struct ConfigReturn
    {
        public RequiredAction requiredAction;
        public bool isValid;
    }

    class Program
    {
        static int Main(string[] args)
        {
            int retVal = 1;
            CmdLine cmdLine = ParseCommandLine(args);
            
            if (cmdLine.parseSucceeded)
            {
                retVal = Configure(cmdLine.configType, cmdLine.userAccount, cmdLine.readOnly) ? 0 : 1;
            }
            else
            {
                Console.WriteLine("Usage: SSISAccess configType [userName] [grant]");
                Console.WriteLine("  configType  May be either server or client");
                Console.WriteLine("  userName    Only valid for configType server. User to check");
                Console.WriteLine("              or grant SSIS access privileges for/to");
                Console.WriteLine("  [grant]     optional: actually perform the necessary changes");
                Console.WriteLine("              If grant is not specified, no changes will be made");
                Console.WriteLine("              and current settings will be displayed for information only");
                Console.WriteLine();
                Console.WriteLine("Example command to run on server to check if a non-admin user can access SSIS:");
                Console.WriteLine("  SSISAccess server contoso\\joe");
                Console.WriteLine();
                Console.WriteLine("Example command to grant SSIS server access to a non-admin user:");
                Console.WriteLine("  SSISAccess server contoso\\joe grant");
                Console.WriteLine();
                Console.WriteLine("Example command to check client machine access to SSIS:");
                Console.WriteLine("  SSISAccess client");
                Console.WriteLine();
                Console.WriteLine("Example command to grant a client machine access to SSIS:");
                Console.WriteLine("  SSISAccess client grant");
            }

            return retVal;
        }

        private static CmdLine ParseCommandLine(string[] args)
        {
            CmdLine retVal = new CmdLine();

            retVal.parseSucceeded = false;
            retVal.configType = ConfigType.Unspecified;
            retVal.readOnly = true;

            if (args.Length >= 1 && args.Length <= 3)
            {
                try
                {
                    retVal.configType = (ConfigType)Enum.Parse(typeof(ConfigType), args[0], true);
                }
                catch
                {
                    Console.WriteLine("Must specify either server or client as the first argument");
                }

                if (retVal.configType == ConfigType.Client)
                {
                    if (args.Length == 2 && args[1].ToLower() == "grant")
                    {
                        retVal.readOnly = false;
                    }
                    retVal.parseSucceeded = true;
                }
                else if (retVal.configType == ConfigType.Server)
                {
                    if (args.Length >= 2)
                    {
                        string userName = args[1];
                        // Verify the given user can be resolved
                        retVal.userAccount = new AccountInfo(userName);
                        try
                        {
                            retVal.userAccount.Initialize();
                            if (args.Length == 3 && args[2].ToLower() == "grant")
                            {
                                retVal.readOnly = false;
                            }

                            retVal.parseSucceeded = true;
                        }
                        catch(Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                            Console.WriteLine("Could not resolve user '{0}'", userName);
                            retVal.userAccount = null;
                        }
                    }
                    else
                    {
                        Console.WriteLine("For server configuration, you must specify a valid username as the second argument");
                    }
                }
            }

            return retVal;
        }

        private static bool Configure(ConfigType config, AccountInfo userAccount, bool readOnly)
        {
            bool retVal = true;
            RequiredAction requiredAction = RequiredAction.None;
            ConfigReturn configReturn = new ConfigReturn();

            Console.WriteLine("{0} configuration,{1} modifications will be made", config.ToString(), readOnly ? " no" : string.Empty);

            if (config == ConfigType.Client)
            {
                // Check the firewall configuration
                configReturn = FirewallConfiguration(readOnly);
                if (configReturn.isValid)
                {
                    Console.WriteLine("Pass: Windows Firewall is configured for remote administration");
                }
                else
                {
                    Console.WriteLine("Fail: Windows Firewall is not configured for remote administration");
                    retVal = false;
                }

                requiredAction = (RequiredAction)Math.Max((int)requiredAction, (int)configReturn.requiredAction);
            }

            // Check if DCOM is enabled on the machine
            configReturn = DCOMEnabled(readOnly);
            if (configReturn.isValid)
            {
                Console.WriteLine("Pass: DCOM is enabled");
            }
            else
            {
                Console.WriteLine("Fail: DCOM is not enabled");
                retVal = false;
            }

            requiredAction = (RequiredAction)Math.Max((int)requiredAction, (int)configReturn.requiredAction);

            if (config == ConfigType.Server)
            {
                // Check if the user is in the local DCOM group
                configReturn = UserInLocalDCOMGroup(userAccount, readOnly);
                if (configReturn.isValid)
                {
                    Console.WriteLine("Pass: {0} is in {1} local group", userAccount.AccountName, LocalDComGroup.GroupName);
                }
                else
                {
                    Console.WriteLine("Fail: {0} is not in {1} local group", userAccount.AccountName, LocalDComGroup.GroupName);
                    retVal = false;
                }

                requiredAction = (RequiredAction)Math.Max((int)requiredAction, (int)configReturn.requiredAction);

                // Check for proper permissions to MsDts
                configReturn = MsDtsPermission(userAccount, readOnly);
                if (configReturn.isValid)
                {
                    Console.WriteLine("Pass: {0} has MsDts launch and access permissions", userAccount.AccountName);
                }
                else
                {
                    Console.WriteLine("Fail: {0} does not have MsDts launch and access permissions", userAccount.AccountName);
                    retVal = false;
                }

                requiredAction = (RequiredAction)Math.Max((int)requiredAction, (int)configReturn.requiredAction);
            }

            // Report required actions to the user
            switch (requiredAction)
            {
                case RequiredAction.None:
                    if (retVal)
                    {
                        Console.WriteLine("No changes or further actions are required");
                    }
                    break;
                case RequiredAction.RestartMachine:
                    if (readOnly)
                    {
                        Console.WriteLine("Machine will require a reboot if changes are made");
                    }
                    else
                    {
                        Console.WriteLine("Machine must be rebooted");
                    }
                    break;
                case RequiredAction.RestartSSIS:
                    if (readOnly)
                    {
                        Console.WriteLine("SSIS service will need to be restarted if changes are made");
                    }
                    else
                    {
                        Console.WriteLine("SSIS service must be restarted");
                    }
                    break;
            }

            if (readOnly && !retVal)
            {
                Console.WriteLine("Specify grant as the last command line parameter to make changes");
            }

            return retVal;
        }

        private static ConfigReturn FirewallConfiguration(bool readOnly)
        {
            ConfigReturn retVal = new ConfigReturn();
            retVal.isValid = false;
            retVal.requiredAction = RequiredAction.None;
            Firewall firewall = new Firewall();

            uint firewallResult = firewall.Initialize();
            if (firewallResult == 0)
            {
                retVal.isValid = firewall.IsRemoteAdministrationEnabled();
                if (!retVal.isValid && !readOnly)
                {
                    Console.WriteLine("Configuring Windows Firewall for remote administration...");
                    firewall.EnableRemoteAdministration(true);
                    retVal.isValid = firewall.IsRemoteAdministrationEnabled();
                    Console.WriteLine(retVal.isValid ? "Complete!" : "Failed!");
                }
            }
            else
            {
                Console.WriteLine("Error 0x{0} connecting to the Windows Firewall service.  The service may not be running.", firewallResult.ToString("X8"));
            }

            return retVal;
        }

        private static ConfigReturn DCOMEnabled(bool readOnly)
        {
            ConfigReturn retVal = new ConfigReturn();
            retVal.isValid = ConfigDCOM.IsEnabled();
            retVal.requiredAction = RequiredAction.None;

            if (!retVal.isValid)
            {
                if (readOnly)
                {
                    retVal.requiredAction = RequiredAction.RestartMachine;
                }
                else
                {
                    Console.WriteLine("Enabling DCOM on this machine...");
                    ConfigDCOM.Enable(true);
                    retVal.isValid = ConfigDCOM.IsEnabled();
                    if (retVal.isValid)
                    {
                        retVal.requiredAction = RequiredAction.RestartMachine;
                        Console.WriteLine("Complete!");
                    }
                    else
                    {
                        Console.WriteLine("Failed!");
                    }
                }
            }
            return retVal;
        }

        private static ConfigReturn UserInLocalDCOMGroup(AccountInfo userAccount, bool readOnly)
        {
            ConfigReturn retVal = new ConfigReturn();
            retVal.isValid = false;
            retVal.requiredAction = RequiredAction.None;

            using (LocalDComGroup dcomGroup = new LocalDComGroup(userAccount))
            {
                retVal.isValid = dcomGroup.IsUserInGroup();

                if (!retVal.isValid)
                {
                    if (readOnly)
                    {
                        retVal.requiredAction = RequiredAction.RestartSSIS;
                    }
                    else
                    {
                        Console.WriteLine("Adding {0} to {1} local group...", userAccount.AccountName, LocalDComGroup.GroupName);
                        dcomGroup.AddUserToGroup();
                        retVal.isValid = dcomGroup.IsUserInGroup();
                        if (retVal.isValid)
                        {
                            retVal.requiredAction = RequiredAction.RestartSSIS;
                            Console.WriteLine("Complete!");
                        }
                        else
                        {
                            Console.WriteLine("Failed!");
                        }
                    }
                }
            }

            return retVal;
        }

        private static ConfigReturn MsDtsPermission(AccountInfo userAccount, bool readOnly)
        {
            ConfigReturn retVal = new ConfigReturn();
            SecurityIdentifier sid = null;
            string accountName = null;

            // If the account that was given at the command line was a domain group
            // it doesn't resolve properly if it is put directly into the MsDts ACLs
            // So, if it is not a user or a local group, substitute the DCOM Users Group
            // as it will resolve correctly and the proper users will have access
            if (userAccount.IsLocalGroup || userAccount.IsUser)
            {
                sid = userAccount.Sid;
                accountName = userAccount.AccountName;
            }
            else
            {
                // Not a local user or local group, have to substitute a local
                // group otherwise it will not resolve correctly and the users will
                // not have access
                //
                // Substituting the local DCOM users group...
                accountName = LocalDComGroup.GroupName;
                NTAccount builtIn = new NTAccount(accountName);
                sid = (SecurityIdentifier)builtIn.Translate(typeof(SecurityIdentifier));
            }

            MsDtsDCOMPermission dtsPermissions = new MsDtsDCOMPermission(sid);
            retVal.isValid = dtsPermissions.HasAccess();
            retVal.requiredAction = RequiredAction.None;

            if (!retVal.isValid)
            {
                if (readOnly)
                {
                    retVal.requiredAction = RequiredAction.RestartSSIS;
                }
                else
                {
                    Console.WriteLine("Granting MsDts launch and access permissions to {0}...", accountName);
                    dtsPermissions.SetAccess();
                    retVal.isValid = dtsPermissions.HasAccess();
                    if (retVal.isValid)
                    {
                        retVal.requiredAction = RequiredAction.RestartSSIS;
                        Console.WriteLine("Complete!");
                    }
                    else
                    {
                        Console.WriteLine("Failed!");
                    }
                }
            }

            return retVal;
        }
    }
}
